from pygame.sprite import *
from pygame.surface import Surface
from pygame import transform
from copy import copy, deepcopy
from helpers import *
from pygame.color import Color

class InvalidPositionError(Exception):
    pass


class MoveWithoutStartingError(Exception):
    pass


class RotateWithoutStartingError(Exception):
    pass


class Piece(Sprite):
    """ Base class for pieces. Subclasses must upcall to it's __init__. The extra param "blocks" which they must pass 
    is a list of x, y tuples which are offsets where blocks of a piece exist, relative to the upper left corner of the 
    bounding box around the entire piece (this.rect).
    """
    def __init__(self, board, color, x, y, size, blocks):
        Sprite.__init__(self)
        # Initialize some variables
        self.board = board # All pieces manipulate the one board. They shouldn't make their own copies
        self.color = color
        self.moveable = True
        self.moving = False
        self.rotation = 0
        self.oldRotation = 0
        self.rotating = False
        self.blockSize = size
        self.blocks = copy(blocks)
        self.originalBlocks = copy(blocks)
        # Figure out the shape of the piece based on blocks
        maxX = 0
        maxY = 0
        for i in range(len(self.blocks)):
            if self.blocks[i][0] > maxX:
                maxX = self.blocks[i][0]
            if self.blocks[i][1] > maxY:
                maxY = self.blocks[i][1]
        shapeWidth = maxX + 1
        shapeHeight = maxY + 1
        # Construct the image for the shape
        imageChunk = Surface((size, size))
        visualColor = color
        # Make green darker than the default interpretation of the word
        if visualColor == 'green':
            visualColor = '0x007700'
        imageChunk.fill(Color(visualColor))
        rectChunk = imageChunk.get_rect()
        self.rect = Rect(x, y, rectChunk.width * shapeWidth, rectChunk.height * shapeHeight)
        self.image = Surface((self.rect.width, self.rect.height))
        self.image = self.image.convert(self.image.get_bitsize(), SRCALPHA)
        # ^ I have no idea why converting it seperately as opposed to creating it with SRCALPHA makes it work, but it does
        self.image.fill((0, 0, 0, 0))
        for i in range(len(self.blocks)):
            self.image.blit(imageChunk, (rectChunk.width * self.blocks[i][0], rectChunk.height * self.blocks[i][1]))
        self.originalImage = self.image
        self.originalRect = copy(self.rect)
    
    def startMoving(self):
        """ Returns true if piece can be moved, False otherwise. """
        
        if self.moveable is True:
            self.moving = True
            self.positionBeforeMove = self._getLeftTop()
            return True
        else:
            return False
    
    def stopMoving(self):
        self.moving = False
        if self.board.rect().colliderect(self.rect):
            try:
                boardCoordinates = self._screenToBoardCoordinates(self.rect)
                cells = [] # Cells on the board that this piece is trying to occupy
                for i in range(len(self.blocks)):
                    iBlockX = boardCoordinates[0] + self.blocks[i][0]
                    iBlockY = boardCoordinates[1] + self.blocks[i][1]
                    if iBlockX < 0 or iBlockX > 19 or iBlockY < 0 or iBlockY > 19:
                        raise InvalidPositionError
                    cells.append((iBlockX, iBlockY))
                self.board.attemptToOccupy(cells, self.color)
                self._setLeftTop((boardCoordinates[0] * self.blockSize + self.board.rect().left, 
                                  boardCoordinates[1] * self.blockSize + self.board.rect().top))
                self.moveable = False
            except InvalidPositionError:
                self._setLeftTop(self.positionBeforeMove)
    
    def moveBy(self, distance):
        if self.moving is False:
            raise MoveWithoutStartingError
        else:
            self.rect.move_ip(distance[0], distance[1])
    
    def startRotating(self):
        """Returns true if piece can be rotated, False otherwise."""
        if self.moveable is True:
            self.rotating = True
            return True
        else:
            return False
    
    def stopRotating(self):
        self.rotating = False
        # Round to nearest multiple of 90 degrees
        self.rotation = round(float(self.rotation % 360) / 90) * 90
        self._rebuildImage()
##        # Update rect, TODO can this just use image.get_rect()?
##        if self.rotation == 90 or self.rotation == 270:
##            self.rect.height = self.originalRect.width
##            self.rect.width = self.originalRect.height
##        else:
##            self.rect.height = self.originalRect.height
##            self.rect.width = self.originalRect.width
        # Update rect & blocks
        self.rect.height = self.originalRect.height
        self.rect.width = self.originalRect.width
        self.blocks = copy(self.originalBlocks)
        def rotate90DegreesCounterclockwise():
            """Updates the classes rect and blocks to be turned 90 degrees counterclockwise."""
            temp = self.rect.height
            self.rect.height = self.rect.width
            self.rect.width = temp
            for i in range(len(self.blocks)):
                newY = int(self.rect.height / self.blockSize) - 1 - self.blocks[i][0]
                newX = self.blocks[i][1]
                self.blocks[i] = (newX, newY)
        for i in range(int(self.rotation / 90)):
            rotate90DegreesCounterclockwise()
    
    def rotateBy(self, degrees):
        """ @param degrees Positive numbers rotate the piece counterclockwise. Only rotates the graphical view of the 
        image, not it's block representation as used by the "game engine".
        """
        if self.rotating is False:
            raise RotateWithoutStartingError
        else:
            self.rotation = self.rotation + degrees
            self._rebuildImage()
    
    def width(self):
        return self.rect.width
    
    def height(self):
        return self.rect.height
    
    def flipVertically(self):
        if self.moveable:
            self.originalImage = transform.flip(self.originalImage, False, True)
            maxY = 0
            for block in self.originalBlocks:
                if block[1] > maxY:
                    maxY = block[1]
            for i in range(len(self.originalBlocks)):
                self.originalBlocks[i] = (self.originalBlocks[i][0], maxY - self.originalBlocks[i][1])
            self.rotation = 360 - self.rotation
            self.stopRotating()
    
    def _rebuildImage(self):
        self.image = transform.rotate(self.originalImage, self.rotation)

    def _setLeftTop(self, position):
        self.rect.left = position[0]
        self.rect.top = position[1]
    
    def _getLeftTop(self):
        return (self.rect.left, self.rect.top)
    
    def _screenToBoardCoordinates(self, screenCoordinates):
        boardX = int(round((float(screenCoordinates[0]) - self.board.rect().left) / self.blockSize))
        boardY = int(round((float(screenCoordinates[1]) - self.board.rect().top) / self.blockSize))
        return (boardX, boardY)


class Single(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0)])


class Line(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0)])


class Tee(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (2, 0), (1, 1)])


class LittleStep(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 1), (1, 1), (1, 0), (2, 0)])


class Double(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (0, 1)])


class TripleLine(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (0, 1), (0, 2)])


class QuadrupleLine(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (2, 0), (3, 0)])


class Box(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (0, 1), (1, 0), (1, 1)])


class Plus(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(1, 0), (0, 1), (1, 1), (2, 1), (1, 2)])


class U(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (2, 0), (0, 1), (1, 1), (2, 1)])


class Steps(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(1, 0), (2, 0), (0, 1), (1, 1), (0, 2)])


class ZigZag(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (1, 1), (1, 2), (2, 2)])


class BigTee(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (2, 0), (1, 1), (1, 2)])


class BigL(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)])


class LittleL(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (1, 1)])


class BigPipe(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1)])


class LittlePipe(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (0, 1), (1, 1), (2, 1)])


class Blob(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(1, 0), (2, 0), (0, 1), (1, 1), (2, 1)])


class Lef(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (1, 1), (2, 1), (1, 2)])


class SignPost(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (0, 1), (1, 1), (0, 2), (0, 3)])


class Ledge(Piece):
    def __init__(self, board, color, x, y, size=25):
        Piece.__init__(self, board, color, x, y, size, blocks = [(0, 0), (1, 0), (2, 0), (2, 1), (3, 1)])


